import { StringDecoder } from 'string_decoder';

import { Codec, CodecOption, Constructor, Decoder, Encoder, Encoding, Options } from '../models.js';

class InternalCodec implements Codec {
  encodingName: string;
  bomAware: boolean;
  decoder: Constructor<Decoder> = InternalDecoder;
  encoder: Constructor<Encoder> = InternalEncoder;

  constructor(codecOptions: CodecOption) {
    this.encodingName = codecOptions.encodingName!;
    this.bomAware = codecOptions.bomAware ?? false;

    if (this.encodingName === 'utf8') {
      this.encoder = InternalEncoderUtf8;
    }
  }
}

/**
 * InternalDecoder 解码器
 */
class InternalDecoder implements Decoder {
  private decoder: Decoder;

  constructor(options: Options, codec: Codec) {
    this.decoder = new StringDecoder(codec.encodingName! as BufferEncoding);
  }

  write(buffer: Buffer | Uint8Array): string {
    const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
    return this.decoder.write(buf);
  }

  end(): string | undefined {
    return this.decoder.end();
  }
}

/**
 * InternalEncoder 编码器
 */
class InternalEncoder implements Encoder {
  private readonly encodingName: string;

  constructor(options: Options, codec: Codec) {
    this.encodingName = codec.encodingName!;
  }

  write(str: string): Buffer {
    return Buffer.from(str, this.encodingName! as BufferEncoding);
  }

  end(): Buffer | undefined {
    return undefined;
  }
}

/**
 * utf8 encoder
 */
class InternalEncoderUtf8 implements Encoder {
  private highSurrogate: string;
  private readonly enc: string;

  constructor(options: Options, codec: Codec) {
    this.highSurrogate = '';
    this.enc = codec.encodingName!;
  }

  write(str: string): Buffer {
    if (this.highSurrogate) {
      str = this.highSurrogate + str;
      this.highSurrogate = '';
    }

    if (str.length > 0) {
      const charCode = str.charCodeAt(str.length - 1);
      if (0xd800 <= charCode && charCode < 0xdc00) {
        this.highSurrogate = str[str.length - 1];
        str = str.slice(0, str.length - 1);
      }
    }

    return Buffer.from(str, this.enc as BufferEncoding);
  }

  end(): Buffer | undefined {
    if (this.highSurrogate) {
      const str = this.highSurrogate;
      this.highSurrogate = '';
      return Buffer.from(str, this.enc as BufferEncoding);
    }
    return undefined;
  }
}

export const InternalEncoding: Encoding = {
  // Encodings
  utf8: { type: '_internal', bomAware: true },

  // Codec.
  _internal: InternalCodec,
};
